package proxy;

import java.io.IOException;
import java.net.InetAddress;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.MissingResourceException;

import message.Response;
import message.response.ListResponse;
import model.FileServerData;
import model.UserSession;
import util.Config;
import util.GenericConfig;

public class Proxy {

	private int tcpPort;
	private int udpPort;
	private int timeout;
	private int checkPeriod;

	private GenericConfig userConfig;

	private Map<String, UserSession> userSessions;
	private Map<Integer, FileServerData> fileServers;

	private int nRead;
	private int nWrite;
	
	private ProxyManagementComponent managementComponent = null;

	public Proxy(Config config) throws MissingResourceException {

		
		/*
		 * creating thread save Maps
		 * 
		 * http://stackoverflow.com/questions/510632/whats-the-difference-between
		 * -concurrenthashmap-and-collections-synchronizedmap
		 */
		userSessions = Collections
				.synchronizedMap(new HashMap<String, UserSession>());
		fileServers = Collections
				.synchronizedMap(new HashMap<Integer, FileServerData>());

		userConfig = new GenericConfig("user");
		
		
		/*
		 * Create User Sessions
		 */
		for (String username : userConfig.getIdentifiers()) {

			int credits = userConfig.getInt(username + ".credits");
			String password = userConfig.getString(username + ".password");

			UserSession session = new UserSession(username, password, credits);

			userSessions.put(username, session);
		}


		tcpPort = config.getInt("tcp.port");
		udpPort = config.getInt("udp.port");
		timeout = config.getInt("fileserver.timeout");
		checkPeriod = config.getInt("fileserver.checkPeriod");
		nRead = 0;
		nWrite = 0;

	}
	
	public void generateUserSessionsForTesting(int numberOfClients) {
		for (int i=1; i<=numberOfClients; i++) {
			UserSession session = new UserSession("client"+i, "12345", 10000000);
			userSessions.put("client"+i, session);
		}
	}
	
	
	
	public void setManagementComponent(ProxyManagementComponent managementComponent) {
		this.managementComponent = managementComponent;
	}
	
	public ProxyManagementComponent getManagementComponent() {
		return this.managementComponent;
	}

	public int getFileServerTimeout() {
		return this.timeout;
	}

	public int getCheckPeriod() {
		return this.checkPeriod;
	}

	public int getUdpPort() {
		return this.udpPort;
	}

	public int getTcpPort() {
		return this.tcpPort;
	}

	public int getReadQuorumCount() {
		return nRead;
	}

	public int getWriteQuorumCount() {
		return nWrite;
	}

	public Collection<UserSession> getUserSessions() {
		return userSessions.values();
	}

	public Collection<FileServerData> getFileServers() {
		return fileServers.values();
	}
	
	public Map<Integer, FileServerData> getFileServerMap() {
		return fileServers;
	}

	public void updateFileservers(InetAddress address, int tcpPort,
			long timestamp) {

		FileServerData fs = null;
		if (fileServers.get(tcpPort) == null) {

			FileserverWorker worker = new FileserverWorker(address, tcpPort);
			fs = new FileServerData(address, tcpPort);

			// Get the filenames from this server
			Response response;
			try {
				response = worker.list();

				if (response instanceof ListResponse)
					fs.setFileNames(((ListResponse) response).getFileNames());
				else
					System.out.println(response.toString());

			} catch (IOException e) {
				e.printStackTrace();
			}

			// register server in proxy
			fileServers.put(tcpPort, fs);
		} else {

			fs = fileServers.get(tcpPort);
			fs.setLastAlivePackage(timestamp);
		}
	}
	
	public List<FileServerData> getAllOnlineFileservers() {
		ArrayList<FileServerData> onlineFs = new ArrayList<FileServerData>();
		
		for(FileServerData fs : fileServers.values()){
			if(fs.isOnline())
				onlineFs.add(fs);
		}
		
		return onlineFs;
	}

	public void calculateQuorum() {
		if(nRead != 0 && nWrite != 0)
			return;
		
		int n = getAllOnlineFileservers().size();
		
		if (n > 0) {
			nWrite = (int) Math.min(Math.ceil(n / 2.0) + 1, n);
			nRead = n - nWrite + 1;
		} else {
			throw new IllegalArgumentException(
					"Parameter n cannot be less than 1");
		}

		if (nRead + nWrite <= n || nWrite <= n / 2.0)
			throw new RuntimeException("Quorum calculation is wrong! (n:" + n
					+ " write:" + nWrite + " read:" + nRead + ")");
	}

	public List<FileServerData> getReadQuorum() {
		List<FileServerData> quorum = getAllOnlineFileservers();
		Collections.sort(quorum);
		
		return quorum.subList(0, nRead);
	}

	public List<FileServerData> getWriteQuorum() {
		List<FileServerData> quorum = getAllOnlineFileservers();
		Collections.sort(quorum);
		
		return quorum.subList(0, nWrite);
	}

	public FileServerData getLeastUsedFileServer(String filename) {

		long minUsage = Long.MAX_VALUE;
		FileServerData leastUsedFs = null;

		// Find least used server which is online and has the needed file
		synchronized (fileServers) {
			for (FileServerData fs : fileServers.values()) {
				if (fs.isOnline() && fs.getUsage() < minUsage
						&& fs.getFileNames().contains(filename)) {
					leastUsedFs = fs;
					minUsage = leastUsedFs.getUsage();
				}
			}
		}
		return leastUsedFs;
	}

	public UserSession authenticate(String username)
			throws MissingResourceException {

		UserSession session = userSessions.get(username);
		
		if (session != null )// && session.authenticate(password))
			return session;
		else
			return null;
	}
}
